use ogg::{PacketReader, PacketWriter};
use pnet_macros::packet;
use pnet_macros_support::{
    packet::{FromPacket, Packet, PacketSize}, types::{u1, u16be, u16le, u2, u32be, u32le, u4, u5, u7}
};

#[packet]
#[derive(Eq, PartialEq)]
pub struct IdentificationHeader {
    #[length = "8"]
    pub magic: Vec<u8>,
    pub version: u8,
    pub channel_count: u8,
    pub pre_skip: u16le,
    pub sample_rate: u32le,
    pub output_gain: u16le,
    pub channel_mapping_family: u8,

    #[payload]
    pub payload: Vec<u8>,
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct CommentHeader {
    #[length = "8"]
    pub magic: Vec<u8>,
    pub vendor_string_len: u32le,
    #[length = "vendor_string_len"]
    pub vendor_string: Vec<u8>,
    pub user_comment_list_len: u32le,
    #[length = "user_comment_list_len"]
    pub user_comment: Vec<UserComment>,

    #[payload]
    pub payload: Vec<u8>,
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct UserComment {
    pub string_len: u32le,
    #[length = "string_len"]
    pub string: Vec<u8>,

    #[payload]
    pub payload: Vec<u8>,
}

pub struct OpusOggReader<R: std::io::Read + std::io::Seek> {
    pub channel_count: usize,
    pub sample_rate: usize,
    reader: PacketReader<R>,
}

impl<R: std::io::Read + std::io::Seek> OpusOggReader<R> {
    pub fn new(stream: R) -> Result<Self, Box<dyn std::error::Error>> {
        let mut reader = PacketReader::new(stream);
        let id_pkt = reader.read_packet()?;
        let _comment_pkt = reader.read_packet()?;
        let id_pkt = id_pkt.and_then(|pkt| IdentificationHeaderPacket::owned(pkt.data));
        if let Some(id_pkt) = id_pkt {
            return Ok(Self {
                channel_count: id_pkt.get_channel_count() as _,
                sample_rate: id_pkt.get_sample_rate() as _,
                reader,
            });
        }
        Err("invalid header".into())
    }
    pub fn read_packet(&mut self) -> Option<Vec<u8>> {
        if let Ok(Some(pkt)) = self.reader.read_packet() {
            Some(pkt.data)
        } else {
            None
        }
    }
}

pub struct OpusOggWriter<'writer, W: std::io::Write> {
    pub channel_count: usize,
    pub sample_rate: usize,
    pub total_duration_msecs: usize,
    writer: PacketWriter<'writer, W>,
}

impl<'writer, W: std::io::Write> OpusOggWriter<'writer, W> {
    pub fn new(
        channel_count: usize,
        sample_rate: usize,
        stream: W,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let mut writer = PacketWriter::new(stream);
        let mut buffer = [0u8; 1024];
        let mut id_pkt = MutableIdentificationHeaderPacket::new(&mut buffer).unwrap();
        id_pkt.set_magic(b"OpusHead");
        id_pkt.set_version(1);
        id_pkt.set_channel_count(channel_count as _);
        id_pkt.set_pre_skip(0);
        id_pkt.set_sample_rate(sample_rate as _);
        id_pkt.set_output_gain(0);
        id_pkt.set_channel_mapping_family(0);
        let id_pkt = id_pkt.packet()[..id_pkt.packet_size()].to_owned();
        writer.write_packet(id_pkt, 0, ogg::PacketWriteEndInfo::EndPage, 0)?;

        let mut comment = MutableCommentHeaderPacket::new(&mut buffer).unwrap();
        comment.set_magic(b"OpusTags");
        comment.set_vendor_string_len(0);
        comment.set_user_comment_list_len(0);
        let comment_pkt = comment.packet()[..comment.packet_size()].to_owned();
        writer.write_packet(comment_pkt, 0, ogg::PacketWriteEndInfo::EndPage, 0)?;

        Ok(Self {
            channel_count,
            sample_rate,
            total_duration_msecs: 0,
            writer,
        })
    }
    pub fn write_packet(&mut self, duration_msecs: usize, pkt: &[u8]) {
        self.total_duration_msecs += duration_msecs;
        let absgp = self.total_duration_msecs * 48;
        let _ = self.writer.write_packet(pkt.to_owned(), 0, ogg::PacketWriteEndInfo::EndPage, absgp as u64);
    }
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct OpusTocHeader {
    pub config: u5,
    pub stereo: u1,
    pub frames_per_packet: u2,
    #[payload]
    pub payload: Vec<u8>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusFrameMode {
    Unknown,
    SilkOnly,
    Hybrid,
    CeltOnly,  
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusFrameBandwidth {
    Unknown,
    NarrowBand,
    MediumBand,
    WideBand,
    SuperWideBand,
    FullBand,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusFrameLength {
    Unknown,
    Millis_2_5,
    Millis_5,
    Millis_10,
    Millis_20,
    Millis_40,
    Millis_60,
}

impl OpusTocHeader {
    pub fn new(buf: &[u8]) -> Option<Self> {
        let pkt = OpusTocHeaderPacket::new(buf)?;
        Some(pkt.from_packet())
    }
    pub fn frame_mode(&self) -> OpusFrameMode {
        match self.config {
            0..=11 => OpusFrameMode::SilkOnly,
            12..=15 => OpusFrameMode::Hybrid,
            16..=31 => OpusFrameMode::CeltOnly,
            _ => OpusFrameMode::Unknown,
        }
    }
    pub fn frame_bandwidth(&self) -> OpusFrameBandwidth {
        match self.config {
            0..=3 | 16..=19 => OpusFrameBandwidth::NarrowBand,
            4..=7 => OpusFrameBandwidth::MediumBand,
            8..=11 | 20..=23 => OpusFrameBandwidth::WideBand,
            12..=13 => OpusFrameBandwidth::SuperWideBand,
            14..=15 | 28..=31 => OpusFrameBandwidth::FullBand,
            _ => OpusFrameBandwidth::Unknown,
        }
    }
    pub fn frame_length(&self) -> OpusFrameLength {
        match self.config {
            0 | 4 | 8 | 12 | 14 | 18 | 22 | 26 | 30 => OpusFrameLength::Millis_10,
            1 | 5 | 9 | 13 | 15 | 19 | 23 | 27 | 31 => OpusFrameLength::Millis_20,
            2 | 6 | 10 => OpusFrameLength::Millis_40,
            3 | 7 | 11 => OpusFrameLength::Millis_60,
            17 | 21 | 25 | 29 => OpusFrameLength::Millis_5,
            16 | 20 | 24 | 28 => OpusFrameLength::Millis_2_5,
            _ => OpusFrameLength::Unknown,
        }
    }
}